<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>力导图</title>
    <script src="https://d3js.org/d3.v6.min.js"></script>
  </head>
  <body>
    <svg
      width="1800"
      height="920"
      id="mainsvg"
      class="svgs"
      style="display: block; margin: 0 auto"
    ></svg>

    <script>
      let svg = d3.select("svg");
      var width = +svg.attr("width");
      var height = +svg.attr("height");
      let nodes, links;
      let circles, lines;
      let simulation;

      const zoom = d3
        .zoom()
        .scaleExtent([0.1, 10]) // Define the zoom scale limits
        .on("zoom", zoomed);
      svg.call(zoom); // Apply zoom behavior to the SVG

      // Create a group that will contain all nodes and links
      const g = svg.append("g");

      function zoomed(event) {
        g.attr("transform", event.transform);
      }

      const render_init = function () {
        lines = g
          .selectAll("line")
          .data(links)
          .join("line")
          .attr("stroke", "black")
          .attr("stroke-width", 0.5)
          .attr("opacity", 0.8); //设置透明度 0-1

        // Add text to each line
        texts = g
          .selectAll("text")
          .data(links)
          .join("text")
          .text("指向")
          .attr("font-size", 10)
          .attr("fill", "black")
          .attr("opacity", 0);

        circles = g
          .selectAll("circle")
          .data(nodes)
          .join("circle")
          .attr("r", 5)
          .attr("title", "节点")
          .attr("fill", (d) => color(d.index))
          .on("mouseover", mouseover)
          .on("mouseout", mouseout)
          .call(
            d3
              .drag()
              .on("start", dragstarted)
              .on("drag", dragged)
              .on("end", dragended)
          );
      };
      function ticked() {
        lines
          .attr("x1", (d) => d.source.x)
          .attr("y1", (d) => d.source.y)
          .attr("x2", (d) => d.target.x)
          .attr("y2", (d) => d.target.y);
        // Update text position to the middle of each line
        texts
          .attr("x", (d) => (d.source.x + d.target.x) / 2)
          .attr("y", (d) => (d.source.y + d.target.y) / 2);

        circles.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
      }
      function tdistance(link) {
        //console.log(link);
        return Math.random() * 100 + 50;
      }

      // Define the drag functions
      function dragstarted(event, d) {
        if (!event.active) simulation.alphaTarget(0.3).restart();
        d.fx = d.x;
        d.fy = d.y;
      }

      function dragged(event, d) {
        d.fx = event.x;
        d.fy = event.y;
      }

      function dragended(event, d) {
        if (!event.active) simulation.alphaTarget(0);
        d.fx = null;
        d.fy = null;
      }

      // Define the mouseover function to show node info and filter neighbors
      function mouseover(event, d) {
        // Show node information (you can customize this)
        console.log(`Node index: ${d.index}`);

        // Highlight the selected node and its neighbors
        const neighbors = links.filter(
          (link) =>
            link.source.index === d.index || link.target.index === d.index
        );
        const neighborNodes = neighbors.map((link) =>
          link.source.index === d.index ? link.target : link.source
        );

        // Hide all lines and circles
        lines.style("opacity", 0);
        circles.style("opacity", 0);
        texts.style("opacity", 0);

        // Highlight the selected node and its neighbors
        circles
          .filter(
            (node) => node.index === d.index || neighborNodes.includes(node)
          )
          .style("opacity", 1);

        lines
          .filter(
            (link) =>
              link.source.index === d.index || link.target.index === d.index
          )
          .style("opacity", 1); //opacity 0-1

        texts
          .filter(
            (link) =>
              link.source.index === d.index || link.target.index === d.index
          )
          .style("opacity", 1);
      }

      // Define the mouseout function to reset the view
      function mouseout(event, d) {
        // Reset the opacity of all lines and circles
        lines.style("opacity", 0.8);
        circles.style("opacity", 1);
        texts.style("opacity", 0);
      }

      d3.json("socfb-Caltech36.json").then((data) => {
        links = data.links;
        nodes = [];
        console.log(links);

        for (let i = 0; i <= data["#nodes"]; i++) {
          nodes.push({ index: i });
        }

        color = d3
          .scaleSequential(d3.interpolateRainbow)
          .domain([0, nodes.length - 1]);

        render_init();

        simulation = d3
          .forceSimulation(nodes)
          .force("manyBody", d3.forceManyBody().strength(-30))
          .force("center", d3.forceCenter(width / 2, height / 2))
          .force("link", d3.forceLink(links).strength(0.1).distance(tdistance))
          .on("tick", ticked);
      });
      // https://aiqicha.baidu.com/patent/info?referId=2be0e0f3edb28882bb7cdc07ea38da106ee6c08a&pid=32062170985626
      // https://zhuanlan.zhihu.com/p/494300250
      // https://blog.csdn.net/qq_32075117/article/details/107492565
      // https://blog.csdn.net/m0_72410588/article/details/130654220
      // https://blog.csdn.net/qq_43442392/article/details/123019354
    </script>
  </body>
</html>
